#include <Common/Detail/FileInputDevice.h>

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

namespace Common {

FileInputDevice::FileInputDevice()
	: m_fileSize(0)
	, m_fd(-1)
	, m_mappedAddr((char*)MAP_FAILED)
	, m_pageSize(sysconf(_SC_PAGE_SIZE))
	, m_mmapFrameSize(sysconf(_SC_PAGE_SIZE))
	, m_offsetByte(0)
	, m_lengthBytes(0)
{
	m_mmapFrameSize = 1024 * sysconf(_SC_PAGE_SIZE);
}

FileInputDevice::~FileInputDevice()
{
	if (-1 != m_fd) close(m_fd);
	if (MAP_FAILED != m_mappedAddr)
		munmap(m_mappedAddr, m_lengthBytes);
}

void FileInputDevice::init(const char *filename) throw (InputDeviceException)
{
	if (-1 != m_fd)
		close(m_fd);
	
	// 1. open the file
	m_fd = open(filename, O_RDONLY);
	
	if (-1 == m_fd)
	{
		char buf[100];
		strerror_r(errno, buf, 100);
		throw InputDeviceException(buf);
	}
	
	// 2. gets it's size
	struct stat fileStat;
	
	if (fstat(m_fd, &fileStat) == -1)
	{
		char buf[100];
		const int errCode = errno;
		sprintf(buf, "Filed to obtaibn file size errno=%d", errCode);
		throw InputDeviceException(buf);
	}
	
	m_fileSize = fileStat.st_size;
}

boost::int64_t FileInputDevice::expectedSize() const
{
	return m_fileSize;	
}

std::size_t FileInputDevice::readBlock(char * buf, boost::int64_t from, boost::int64_t to)  throw (InputDeviceException)
{
	if (-1 == m_fd)
		throw InputDeviceException("read file block failed : file is not opened");

	if (from > to)
		throw InputDeviceException("read file block failed indexes out of range");
	
	if (from > m_fileSize)
		return 0; // nothing to read
	if (to > m_fileSize)
		to = m_fileSize;
	
	const boost::int64_t reqLenght = to - from;
	char * bufPtr = buf;
	std::size_t readBytes = 0;
	
	
	// read loop	
	while (true)
	{		
		// Check if required bytes is already in the mapping
		if (MAP_FAILED != m_mappedAddr)
		{
			const off_t endOffset = m_offsetByte + m_lengthBytes;
			if (from >= m_offsetByte && from < endOffset)
			{
				std::size_t length = endOffset - from;
				bool completed = false;
				if (endOffset >= to)
				{
					length -= endOffset - to;
					completed = true;				
				}
	

				
				const std::size_t localOffset = from - m_offsetByte;
				
				memcpy(bufPtr, m_mappedAddr + localOffset, length);
				readBytes += length;
				bufPtr += length;
				
				if (completed)
					return readBytes;
					
				from += length;
			}
		}

		if (MAP_FAILED != m_mappedAddr)	
		{
			munmap(m_mappedAddr, m_mmapFrameSize);
			m_mappedAddr = (char *)MAP_FAILED;
		}

		m_offsetByte =  from & ~(m_pageSize - 1); // align offset by page
		m_lengthBytes = m_mmapFrameSize;
		

		m_mappedAddr = (char *) mmap(NULL, m_lengthBytes, PROT_READ, MAP_PRIVATE, m_fd, m_offsetByte);
		if (MAP_FAILED == m_mappedAddr)
		{
			char buf[100];
			const int errCode = errno;
			sprintf(buf, "File mapping failed errno=%d", errCode);
			throw InputDeviceException(buf);
		}
	
	}	

	return readBytes;
}
	
} // namespace common
